package flatstore

import (
	"container/list"
	"os"
	"sync"
)

const (
	DefaultMaxCachedFd = 1000
)

type FdCache struct {
	mu       sync.Mutex
	list     *list.List
	cache    map[int]*list.Element
	size     int
	capacity int
}

type Entry struct {
	Key    int
	Val    *os.File
	Length int64
}

func NewFdCache(maxCachedFd int) *FdCache {
	if maxCachedFd < DefaultMaxCachedFd {
		maxCachedFd = DefaultMaxCachedFd
	}
	return &FdCache{
		list:     list.New(),
		cache:    make(map[int]*list.Element),
		capacity: maxCachedFd,
	}
}

func (fc *FdCache) Add(idx int, fp *os.File, length int64) {
	fc.mu.Lock()
	if e, ok := fc.cache[idx]; ok {
		e.Value.(*Entry).Length = length
		fc.updatePlace(e)
	} else {
		fc.insert(idx, fp, length)
	}

	fc.mu.Unlock()
}

func (fc *FdCache) Get(idx int) (fp *os.File, length int64, hit bool) {
	fc.mu.Lock()
	if e, ok := fc.cache[idx]; ok {
		fc.updatePlace(e)
		eEntry := e.Value.(*Entry)
		fp = eEntry.Val
		length = eEntry.Length
		hit = true
	}
	fc.mu.Unlock()

	return
}

func (fc *FdCache) Delete(idx int) {
	fc.mu.Lock()
	if e, ok := fc.cache[idx]; ok {
		fc.remove(e)
	}

	fc.mu.Unlock()
}

func (fc *FdCache) insert(idx int, fp *os.File, length int64) {
	element := fc.list.PushFront(&Entry{Key: idx, Val: fp, Length: length})
	fc.cache[idx] = element
	fc.size++
	fc.checkCapacity()
}

func (fc *FdCache) updatePlace(e *list.Element) {
	fc.list.MoveToFront(e)
}

func (fc *FdCache) checkCapacity() {
	for fc.size > fc.capacity {
		e := fc.list.Back()
		fc.remove(e)
	}
}

func (fc *FdCache) remove(e *list.Element) {
	eEntry := e.Value.(*Entry)
	if eEntry.Val != nil {
		eEntry.Val.Close()
	}
	fc.list.Remove(e)
	delete(fc.cache, eEntry.Key)
	fc.size--
}
